﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;

namespace Aspects.PInvoke
{
    public partial class Native
    {
        #region Structures
        
        #region Wifi Interface
        /// <summary>
        /// Contains an array of NIC information
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct WLAN_INTERFACE_INFO_LIST
        {
            public Int32 dwNumberOfItems;
            public Int32 dwIndex;
            public WLAN_INTERFACE_INFO[] InterfaceInfo;
            public WLAN_INTERFACE_INFO_LIST(IntPtr pList)
            {
                // The first 4 bytes are the number of WLAN_INTERFACE_INFO structures.
                dwNumberOfItems = Marshal.ReadInt32(pList, 0);
    
                // The next 4 bytes are the index of the current item in the unmanaged API.
                dwIndex = Marshal.ReadInt32(pList, 4);
    
                // Construct the array of WLAN_INTERFACE_INFO structures.
                InterfaceInfo = new WLAN_INTERFACE_INFO[dwNumberOfItems];
    
                for (int i = 0; i < dwNumberOfItems; i++)
                {
                // The offset of the array of structures is 8 bytes past the beginning. Then, take the index and multiply it by the number of bytes in the structure.
                // the length of the WLAN_INTERFACE_INFO structure is 532 bytes - this was determined by doing a sizeof(WLAN_INTERFACE_INFO) in an unmanaged C++ app.
                IntPtr pItemList = new IntPtr(pList.ToInt32() + (i * 532) + 8);
    
                // Construct the WLAN_INTERFACE_INFO structure, marshal the unmanaged structure into it, then copy it to the array of structures.
                WLAN_INTERFACE_INFO wii = new WLAN_INTERFACE_INFO();
                wii = (WLAN_INTERFACE_INFO)Marshal.PtrToStructure(pItemList, typeof(WLAN_INTERFACE_INFO));
                InterfaceInfo[i] = wii;
                }            
            }
        }
        
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct WLAN_INTERFACE_INFO
        {
            public Guid InterfaceGuid;
            /// WCHAR[256]
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string strInterfaceDescription;
            public WLAN_INTERFACE_STATE isState;
        }
        
        #endregion
        
        //used to find device info from device manager
        [StructLayout(LayoutKind.Sequential)]
        private struct SP_DEVINFO_DATA
        {
            public UInt32 cbSize;
            public Guid classGuid;
            public UInt32 devInst;
            public IntPtr reserved;
        } 
        
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct IDSECTOR
        {
            public short wGenConfig;
            public short wNumCyls;
            public short wReserved;
            public short wNumHeads;
            public short wBytesPerTrack;
            public short wBytesPerSector;
            public short wSectorsPerTrack;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public short[] wVendorUnique;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public byte[] sSerialNumber;
            public short wBufferType;
            public short wBufferSize;
            public short wECCSize;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] sFirmwareRev;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
            public byte[] sModelNumber;
            public byte wMoreVendorUnique;
            public short wDoubleWordIO;
            public short wCapabilities;
            public short wReserved1;
            public short wPIOTiming;
            public short wDMATiming;
            public short wBS;
            public short wNumCurrentCyls;
            public short wNumCurrentHeads;
            public short wNumCurrentSectorsPerTrack;
            public long ulCurrentSectorCapacity;
            public short wMultSectorStuff;
            public long ulTotalAddressableSectors;
            public short wSingleWordDMA;
            public short wMultiWordDMA;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
            public byte[] bReserved;
        }

        /// <summary>
        /// The information structure that is retrieved with GetVersionEx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct osviINFOEX
        {
            public int dwosviInfoSize;
            public int osMajorVersion;
            public int osMinorVersion;
            public int osBuildNumber;
            public int platformId;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string servicePackString;
            public short servicePackMajor;
            public short servicePackMinor;
            public short suiteMask;
            public byte wProductType;
            private byte wReserved;

            public int GetSize()
            {
                return Marshal.SizeOf(typeof(osviINFOEX));
            }
        }

        /// <summary>
        /// Memory structure used when retrieving Global Memory Status
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public class MEMORYSTATUSEX
        {
            public uint dwLength;
            public uint dwMemoryLoad;
            public ulong ullTotalPhys;
            public ulong ullAvailPhys;
            public ulong ullTotalPageFile;
            public ulong ullAvailPageFile;
            public ulong ullTotalVirtual;
            public ulong ullAvailVirtual;
            public ulong ullAvailExtendedVirtual;

            public void Instantiate()
            {
                dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
            }
        }

        #endregion

        #region Enumerators

        public enum WLAN_INTERFACE_STATE
        {
            wlan_interface_state_not_ready = 0,
            wlan_interface_state_connected = 1,
            wlan_interface_state_ad_hoc_network_formed = 2,
            wlan_interface_state_disconnecting = 3,
            wlan_interface_state_disconnected = 4,
            wlan_interface_state_associating = 5,
            wlan_interface_state_discovering = 6,
            wlan_interface_state_authenticating = 7,
        }
        
        #endregion

        #region Constants

        private const uint DIGCF_PRESENT = 2;
        private const uint ERROR_INVALID_DATA = 13;
        private const uint ERROR_NO_MORE_ITEMS = 259;
        private const uint ERROR_ELEMENT_NOT_FOUND = 1168;

        #endregion
        
        #region kernel32.dll

        /// <summary>
        /// Retrieves information about the current operating system.
        /// </summary>
        /// <param name="osvi">An OSVERSIONINFO or OSVERSIONINFOEX structure that receives the operating system information. Before calling the GetVersionEx function, set the dwOSVersionInfoSize member of the structure as appropriate to indicate which Data structure is being passed to this function.</param>
        /// <returns>If the function succeeds, the return value is a nonzero value</returns>
        [DllImport("kernel32")]
        public static extern bool GetVersionEx(ref osviINFOEX osvi);

        /// <summary>
        /// Retrieves information about the system's current usage of both physical and virtual memory
        /// </summary>
        /// <param name="lpBuffer">A pointer to a MEMORYSTATUSEX structure that receives information about current memory availability</param>
        /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.</returns>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool GlobalMemoryStatusEx(MEMORYSTATUSEX lpBuffer);

        /// <summary>
        /// Closes the Handle pointed to
        /// </summary>
        /// <param name="hObject">The handle to close</param>
        /// <returns>true upon successful completion</returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr hObject);
        
        /// <summary>
        /// Retrieves a string from the specified section in an initialization file.
        /// </summary>
        /// <param name="lpAppName">The name of the section containing the key name. If this parameter is NULL, the GetPrivateProfileString function copies all section names in the file to the supplied buffer.</param>
        /// <param name="lpKeyName">The name of the key whose associated string is to be retrieved. If this parameter is NULL, all key names in the section specified by the lpAppName parameter are copied to the buffer specified by the lpReturnedString parameter.</param>
        /// <param name="lpDefault">A default string. If the lpKeyName key cannot be found in the initialization file, GetPrivateProfileString copies the default string to the lpReturnedString buffer. If this parameter is NULL, the default is an empty string, "".</param>
        /// <param name="lpReturnString">A pointer to the buffer that receives the retrieved string. </param>
        /// <param name="nSize">The size of the buffer pointed to by the lpReturnedString parameter, in characters.</param>
        /// <param name="lpFilename">The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.</param>
        /// <returns>The return value is the number of characters copied to the buffer, not including the terminating null character.</returns>
        [DllImport("kernel32.dll", EntryPoint = "GetPrivateProfileStringW", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        private static extern int GetPrivateProfileString(string lpAppName,string lpKeyName,string lpDefault,string lpReturnString,int nSize,string lpFilename);
        
        /// <summary>
        /// Copies a string into the specified section of an initialization file.
        /// </summary>
        /// <param name="lpAppName">The name of the section to which the string will be copied. If the section does not exist, it is created. The name of the section is case-independent; the string can be any combination of uppercase and lowercase letters.</param>
        /// <param name="lpKeyName">The name of the key to be associated with a string. If the key does not exist in the specified section, it is created. If this parameter is NULL, the entire section, including all entries within the section, is deleted.</param>
        /// <param name="lpString">A null-terminated string to be written to the file. If this parameter is NULL, the key pointed to by the lpKeyName parameter is deleted. </param>
        /// <param name="lpFilename">The name of the initialization file.</param>
        /// <returns>If the function successfully copies the string to the initialization file, the return value is nonzero.</returns>
        [DllImport("kernel32.dll", EntryPoint="WritePrivateProfileStringW", SetLastError=true,CharSet=CharSet.Unicode, ExactSpelling=true,CallingConvention=CallingConvention.StdCall)]
        public static extern int WriteIniFile(string lpAppName,string lpKeyName,string lpString,string lpFilename);
        
        #endregion
        
        #region setupapi.dll
        
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid,[MarshalAs(UnmanagedType.LPWStr)]string Enumerator,IntPtr parent,UInt32 flags);
        
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
    
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,UInt32 memberIndex,[Out] out SP_DEVINFO_DATA deviceInfoData);        
        
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern bool SetupDiGetDeviceRegistryPropertyW(IntPtr DeviceInfoSet,[In] ref SP_DEVINFO_DATA  DeviceInfoData,UInt32 Property,[Out] out UInt32  PropertyRegDataType,IntPtr PropertyBuffer,UInt32 PropertyBufferSize,[In,Out] ref UInt32 RequiredSize);
        
        #endregion
        
        #region WLanApi.dll
        
        [DllImport("Wlanapi.dll", SetLastError = true)]
        private static extern int WlanOpenHandle(uint dwClientVersion,IntPtr pReserved, [Out] out uint pdwNegotiatedVersion,ref IntPtr ClientHandle);
        
        [DllImport("Wlanapi.dll", SetLastError = true)]
        private static extern uint WlanEnumInterfaces([In] IntPtr hClientHandle, IntPtr pReserved, ref IntPtr ppInterfaceList);   
        
        #endregion

        #region inpout32.dll
        
        [DllImport("inpout32.dll")]
        public extern static ushort Inp32(ushort address);

        [DllImport("inpout32.dll")]
        public extern static ushort Out32(ushort address, ushort data);

        #endregion

        #region Public Calls

        public static IDSECTOR GetHddIdentification()
        {
            int dataSize = Marshal.SizeOf(typeof(IDSECTOR));
            IntPtr dataPointer = Marshal.AllocHGlobal(dataSize);
            Marshal.Copy(GetHddSmartData(), 0, dataPointer, dataSize);
            IDSECTOR data = (IDSECTOR)Marshal.PtrToStructure(dataPointer, typeof(IDSECTOR));
            Marshal.FreeHGlobal(dataPointer);
            
            return data;
        }
        
        public static List<string> GetCategories(string iniFile)
        {
            string returnString = new string(' ', 65536);
            GetPrivateProfileString(null, null, null, returnString, 65536, iniFile);
            returnString = returnString.Trim();
            var charMy = returnString.ToCharArray();
            List<string> result = new List<string>(returnString.Split('\0'));
            result.RemoveRange(result.Count-2,2);
            return result;
        }
        
        public static List<string> GetKeys(string iniFile, string category)
        {
            string returnString = new string(' ', 32768);
            GetPrivateProfileString(category, null, null, returnString, 32768, iniFile);
            returnString.Trim();
            List<string> result = new List<string>(returnString.Split('\0'));
            result.RemoveRange(result.Count-2,2);
            return result;
        }
        
        public static string GetIniFileString(string iniFile, string category, string key, string defaultValue)
        {
            string returnString = new string(' ', 1024);
            GetPrivateProfileString(category, key, defaultValue, returnString, 1024, iniFile);
            returnString = returnString.Trim();
            return returnString.Split('\0')[0];
        }
        
        #region Network
        
        public static bool IsDevicePresent(string guid)
        {
            var info = IntPtr.Zero;
            var NullGuid = new Guid(guid);
            try
            {
                info = SetupDiGetClassDevsW(ref NullGuid,null,IntPtr.Zero,DIGCF_PRESENT);
                CheckError();
                var devdata = new SP_DEVINFO_DATA();
                devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
                // Get first device matching device criterion.
                SetupDiEnumDeviceInfo(info,0,out devdata);
                // if no items match filter, throw
                CheckError();
            }
            catch
            {
                return false;
            }
            finally
            {
                if (info != IntPtr.Zero)
                    SetupDiDestroyDeviceInfoList(info);
            }
            return true;
        }
        
        public static bool IsWifiPresent()
        {
            IntPtr WlanHandle = IntPtr.Zero;
            uint negotiatedVersion;
            try
            {
                var openSuccess = WlanOpenHandle(2, IntPtr.Zero, out negotiatedVersion, ref WlanHandle);
                CheckError();
                
                IntPtr pointer = new IntPtr();
                var enumSuccess = WlanEnumInterfaces(WlanHandle, IntPtr.Zero, ref pointer);
                CheckError();
                WLAN_INTERFACE_INFO_LIST wlanList = new WLAN_INTERFACE_INFO_LIST(pointer);
                return wlanList.dwNumberOfItems > 0;
            }
            catch
            {
                return false;
            }
        }
        
        private static void CheckError()
        {
            int code = Marshal.GetLastWin32Error();
            if (code != 0)
                throw new ApplicationException(String.Format("Error Finding Device, probably doesn't exist. (Code {0})",code));
        }
        
        #endregion
        
        #endregion
    }
}